#!/usr/bin/python
# -*- coding: utf-8 -*-
# @Time    : 2020/8/25 11:28
# @Author  : JY.Liu
# @Site    : http://github.com/lh1993
# @Mail    : lhln0119@163.com
# @File    : logstore_monitor.py
# @Software: PyCharm

import datetime
import time
import re
from tools.aliyun import AliyunSLS
from tools.wechat import WeChatSent
from tools.my_redis import Redis
from tools.db_exec import save_sls_alert
from tools import get_config


def markdown_alarm(
        trigger,
        namespace,
        container_name,
        firetime,
        content):
    msg = """
### <font color=\"warning\">告警发生</font>
>**关键字**：%s
>**命名空间**：%s
>**容器**：%s
>**时间**：%s
>**内容**：%s

""" % (trigger, namespace, container_name, firetime, content)
    return msg


def markdown_restore(
        trigger,
        namespace,
        container_name,
        firetime,
        content):
    msg = """
### <font color=\"info\">告警恢复</font>
>**关键字**：%s
>**命名空间**：%s
>**容器**：%s
>**时间**：%s
>**内容**：%s

""" % (trigger, namespace, container_name, firetime, content)
    return msg


async def exec_logstore_monitor(project, logstore, query, fromTime, toTime):
    A = AliyunSLS()
    W = WeChatSent()
    R = Redis()
    black_logstore_list = get_config.black_logstore.split('|')
    print("=" * 50, flush=True)
    print("logstore: %s" % logstore, flush=True)
    # 存储告警信息
    logstore_alarm_info_key = logstore + "_alarm_info"
    # 告警发送标志，告警沉默用
    logstore_alarm_key = logstore + "_alarm"
    # 统计告警触发次数，配置告警触发条件使用
    logstore_counts_key = logstore + "_counts"
    # 异常UA
    abnormal_ua_key = logstore + "_abnormal_ua"
    # 如果日志库在黑名单在中，忽略
    if logstore in black_logstore_list:
        return "[%s] 该日志库存在于黑名单中" % logstore
    # 根据条件，进行日志查询
    logs_info_list = A.getLogs(
        project=project,
        logstore=logstore,
        query=query,
        fromTime=fromTime,
        toTime=toTime)

    if logs_info_list:
        try:
            # 截取日志内容
            content = "未匹配到日志内容字段的关键字"
            if 'content' in logs_info_list[0].keys():
                content = logs_info_list[0]['content'][:2048]
            elif 'message' in logs_info_list[0].keys():
                content = logs_info_list[0]['message'][:2048]
            else:
                pass
            # 默认存在异常UA，设置为True
            flag = True
            for item in logs_info_list:
                content = ""
                if 'content' in logs_info_list[0].keys():
                    content = item['content']
                elif 'message' in logs_info_list[0].keys():
                    content = item['message']
                else:
                    pass

                # 判断异常关键字是否存在于告警忽略的日志库中
                for ignore_tuple in get_config.ignore_list:
                    # 如果特殊关键字存在日志中
                    re_search = re.search(ignore_tuple[0], content, re.IGNORECASE)
                    if re_search:
                        # 日志库存在于忽略列表，则不触发告警
                        if logstore in ignore_tuple[1].split('|'):
                            print("[%s] 日志库, 忽略关键字 [%s]" % (logstore, ignore_tuple[0]), flush=True)
                            return "[%s] 日志库, 忽略关键字 [%s]" % (logstore, ignore_tuple[0])

                        # 判断告警日志中是否存在生产环境出口IP
                        # 正则抓取IP地址
                        client_ip_list = re.findall(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', content)
                        for client_ip in client_ip_list:
                            if client_ip in ignore_tuple[1].split('|'):
                                # print("[%s] 日志库, 生产出口IP [%s] 在调用" % (logstore, client_ip), flush=True)
                                flag = False
                                break
                        if flag is True:
                            # 日志中没有出口IP，异常UA+1
                            if R.exists(abnormal_ua_key) == 1:
                                R.incr(abnormal_ua_key)
                            else:
                                R.set(abnormal_ua_key, "1")
                                R.expire(abnormal_ua_key, get_config.expire_second)
                            content = content[:2048]

            if R.exists(abnormal_ua_key) == 1:
                # 日志中不存在生产IP地址，且异常UA访问达到限制的数量
                if R.get(abnormal_ua_key) > get_config.max_abnormal_ua_counts:
                    print("存在异常UA，触发告警")
                else:
                    return "[%s] 日志库, 存在少量异常UA，暂不触发告警"
            else:
                if flag is False:
                    return "[%s] 日志库, 生产出口IP在调用" % logstore

            # 日志多行处理
            content = content.split("\n")
            if len(content) > 1:
                new_content = ""
                new_content += content[0] + "\n"
                for item in content[1:]:
                    new_content += ">" + item + "\n"
            else:
                new_content = content[0]

            # 如果日志库在redis中，说明最近发生过告警，开始沉默，直到key过期
            if R.exists(logstore_alarm_key) == 1:
                return "[%s] 告警沉默" % logstore
            # 周期性检查结果不为空，设置日志库触发的次数
            if R.exists(logstore_counts_key) == 1:
                R.incr(logstore_counts_key)
            else:
                R.set(logstore_counts_key, "1")
                R.expire(logstore_counts_key, get_config.expire_second)
            # 告警条件触发次数大于或等于设置的触发次数，则执行告警操作
            if R.get(logstore_counts_key) >= get_config.trigger_counts:
                try:
                    namespace = logs_info_list[0]['_namespace_']
                except Exception as e:
                    namespace = logs_info_list[0]['__tag__:_namespace_']

                try:
                    container_name = logs_info_list[0]['_container_name_']
                except Exception as e:
                    container_name = logs_info_list[0]['__tag__:_container_name_']

                try:
                    sls_time = logs_info_list[0]['_time_'].replace(
                        'T', ' ').replace('Z', '').split('.')
                    sls_time = datetime.datetime.strptime(
                        sls_time[0], '%Y-%m-%d %H:%M:%S')
                    firetime = sls_time + datetime.timedelta(hours=8)
                    firetime = datetime.datetime.strftime(
                        firetime, '%Y-%m-%d %H:%M:%S')
                except Exception as e:
                    firetime = ""

                print("query: %s" % query, flush=True)
                print("namespace: %s" % namespace, flush=True)
                print("container_name: %s" % container_name, flush=True)
                print("firetime: %s" % firetime, flush=True)
                print("content: %s" % new_content, flush=True)
                # 告警日志库及告警信息存到redis，设置过期时间
                alarm_dict = {
                    "namespace": namespace,
                    "container_name": container_name,
                    "firetime": firetime,
                    "content": new_content
                }
                R.set(logstore_alarm_key, "0")
                R.expire(logstore_alarm_key, get_config.expire_second)
                R.hmset(logstore_alarm_info_key, alarm_dict)
                R.expire(logstore_alarm_info_key, get_config.expire_second)
                print("告警信息-->Redis", flush=True)
                startTime = time.time()
                # 发送到企业微信应用
                msg = markdown_alarm(
                    trigger=query,
                    namespace=namespace,
                    container_name=container_name,
                    firetime=firetime,
                    content=new_content)
                print("[%s] Wait for wechat response" % logstore, flush=True)
                # await 可以将耗时等待的操作挂起，让出控制权
                result = await W.send_markdown(content=msg)
                print("[%s] Get response Result: %s" % (logstore, result), flush=True)
                print("告警发生-->企业微信", flush=True)
                endTime = time.time()
                print("微信耗时：%s" % (endTime - startTime), flush=True)
        except Exception as e:
            print("异常：%s" % e, flush=True)
        try:
            startTime = time.time()
            namespace = logs_info_list[0]['_namespace_']
            container_name = logs_info_list[0]['_container_name_']
            sls_time = logs_info_list[0]['_time_'].replace(
                'T', ' ').replace('Z', '').split('.')
            sls_time = datetime.datetime.strptime(
                sls_time[0], '%Y-%m-%d %H:%M:%S')
            firetime = sls_time + datetime.timedelta(hours=8)
            firetime = datetime.datetime.strftime(
                firetime, '%Y-%m-%d %H:%M:%S')
            Sql1 = "REPLACE INTO sls_alert (firetime, container_name) VALUES ('%s', '%s');" % (
                firetime, container_name)
            Sql2 = """
                      update sls_alert
                      set
                         namespace='%s',
                         query='%s'
                      where
                         firetime='%s'and container_name='%s';
                  """ % (namespace,
                         query,
                         firetime,
                         container_name)
            await save_sls_alert(sql=Sql1)
            await save_sls_alert(sql=Sql2)
            print("数据入库-->报告库", flush=True)
            endTime = time.time()
            print("入库耗时：%s" % (endTime - startTime), flush=True)
        except Exception as e:
            print("异常：%s" % e, flush=True)
    else:
        try:
            # 如果{logstore_alarm}存在在redis中，说明最近发生过告警
            if R.exists(logstore_alarm_key) == 1:
                # 告警恢复,计数-1
                R.decr(logstore_alarm_key)
                # 判断告警恢复计数的绝对值是否大于或等于设置的告警恢复次数
                if abs(int(R.get(logstore_alarm_key))) >= int(
                        get_config.check_period):
                    try:
                        startTime = time.time()
                        logstore_alarm_info = R.hgetall(
                            logstore_alarm_info_key)
                        # 发送到企业微信应用
                        msg = markdown_restore(
                            trigger=query,
                            namespace=logstore_alarm_info['namespace'],
                            container_name=logstore_alarm_info['container_name'],
                            firetime=logstore_alarm_info['firetime'],
                            content=logstore_alarm_info['content'])
                        # await 可以将耗时等待的操作挂起，让出控制权
                        print("[%s] Wait for wechat response" % logstore, flush=True)
                        result = await W.send_markdown(content=msg)
                        print("[%s] Get response Result: %s" % (logstore, result), flush=True)
                        print("告警恢复-->企业微信", flush=True)
                        endTime = time.time()
                        print("微信耗时：%s" % (endTime - startTime), flush=True)
                    except Exception as e:
                        print("异常：%s" % e, flush=True)
                    finally:
                        # 如果告警恢复，则删除redis中日志库的key
                        R.delete(logstore_counts_key)
                        R.delete(logstore_alarm_key)
                        R.hdel(logstore_alarm_info_key)
        except Exception as e:
            print("异常：%s" % e, flush=True)
    print("=" * 50, flush=True)
    return "[%s] 日志库检查结束" % logstore
